package com.cch.cooperation.api.zone.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cch.cooperation.api.auth.service.UserDeviceServiceImpl;
import com.cch.cooperation.api.auth.service.UserServiceImpl;
import com.cch.cooperation.dao.file.dto.FileDTO;
import com.cch.cooperation.api.support.dto.PushAppNoticeDTO;
import com.cch.cooperation.api.support.service.ContentCheckServiceImpl;
import com.cch.cooperation.api.support.service.PushServiceImpl;
import com.cch.cooperation.api.zone.dto.*;
import com.cch.cooperation.api.zone.event.ZoneRemindCompleteEvent;
import com.cch.cooperation.biz.common.lock.LockerProxy;
import com.cch.cooperation.biz.common.util.BeanUtilEx;
import com.cch.cooperation.biz.common.util.SpringUtil;
import com.cch.cooperation.common.exception.BizException;
import com.cch.cooperation.common.util.SnowflakeIdUtil;
import com.cch.cooperation.dao.auth.model.User;
import com.cch.cooperation.dao.zone.dto.ZoneDTO;
import com.cch.cooperation.dao.zone.enums.ZoneRemindEnum;
import com.cch.cooperation.dao.zone.enums.ZoneRemindItemEnum;
import com.cch.cooperation.dao.zone.mapper.ZoneRemindMapper;
import com.cch.cooperation.dao.zone.model.ZoneRemind;
import com.cch.cooperation.dao.zone.model.ZoneRemindItem;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Service
public class ZoneRemindServiceImpl {

    @Resource
    private ZoneRemindMapper mapper;
    @Resource
    private ZoneRemindItemServiceImpl zoneRemindItemService;
    @Resource
    private ZoneServiceImpl zoneService;
    @Resource
    private UserServiceImpl userService;
    @Resource
    private PushServiceImpl pushService;
    @Resource
    private LockerProxy lockerProxy;
    @Resource
    private ContentCheckServiceImpl contentCheckService;
    @Resource
    private UserDeviceServiceImpl userDeviceService;

    private static final ThreadPoolExecutor generateRemindExecutor = new ThreadPoolExecutor(10, 20, 10L, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy());

    public ZoneRemindDTO add(@Valid ZoneRemindAddDTO req) {
        ZoneRemind remind = convertToZoneRemind(req);
        // 内容审核
        contentCheckService.checkText(remind.getContent());
        remind.setCreateTime(new Date());
        mapper.insert(remind);
        return queryList(req.getCreateUserId(), Lists.newArrayList(req.getZoneId())).stream().filter(o -> o.getZoneRemindId().equals(remind.getId())).findFirst().orElse(null);
    }

    public ZoneRemindHomeDTO getHomeList(@Valid ZoneRemindHomeQueryDTO req) {
        ZoneRemindQueryDTO listQuery = BeanUtilEx.copy(req, ZoneRemindQueryDTO.class);
        listQuery.setBeginTime(null);
        listQuery.setEndTime(null);
        List<ZoneRemindDTO> list = queryList(listQuery);
        List<ZoneRemindDTO> unCompletes = list.stream().filter(o -> Objects.equals(o.getStatus(), ZoneRemindItemEnum.Status.UN_COMPLETE.value)).collect(Collectors.toList());
        List<ZoneRemindDTO> completes = list.stream().filter(o -> Objects.equals(o.getStatus(), ZoneRemindItemEnum.Status.COMPLETED.value)).collect(Collectors.toList());
        if (Objects.nonNull(req.getBeginTime())) {
            completes = completes.stream().filter(o -> Objects.nonNull(o.getCompleteTime()) && o.getCompleteTime().getTime() >= req.getBeginTime().getTime()).collect(Collectors.toList());
            unCompletes = unCompletes.stream().filter(o -> Objects.isNull(o.getRemindTime()) || o.getRemindTime().getTime() >= req.getBeginTime().getTime()).collect(Collectors.toList());
        }
        if (Objects.nonNull(req.getEndTime())) {
            completes = completes.stream().filter(o -> Objects.nonNull(o.getCompleteTime()) && o.getCompleteTime().getTime() <= req.getEndTime().getTime()).collect(Collectors.toList());
            unCompletes = unCompletes.stream().filter(o -> Objects.isNull(o.getRemindTime()) || o.getRemindTime().getTime() <= req.getEndTime().getTime()).collect(Collectors.toList());
        }
        ZoneRemindHomeDTO result = new ZoneRemindHomeDTO();
        result.setItems(unCompletes.stream().filter(o -> Objects.nonNull(o.getRemindTime())).collect(Collectors.toList()));
        result.setFreeItems(unCompletes.stream().filter(o -> Objects.isNull(o.getRemindTime())).collect(Collectors.toList()));
        result.setCompletedItems(completes);
        return result;
    }

    public List<ZoneRemindDTO> queryList(ZoneRemindQueryDTO query) {
        if (StrUtil.isNotEmpty(query.getZoneId())) {
            query.setZoneIds(Lists.newArrayList(query.getZoneId()));
        }
        List<ZoneRemindDTO> result = queryList(query.getUserId(), query.getZoneIds());
        Stream<ZoneRemindDTO> stream = result.stream();
        if (Objects.nonNull(query.getRepeatType())) {
            stream = stream.filter(o -> Objects.equals(o.getRepeatType(), query.getRepeatType()));
        }
        if (Objects.nonNull(query.getBeginTime())) {
            stream = stream.filter(o -> Objects.nonNull(o.getRemindTime()) && o.getRemindTime().getTime() >= query.getBeginTime().getTime());
        }
        if (Objects.nonNull(query.getEndTime())) {
            stream = stream.filter(o -> Objects.nonNull(o.getRemindTime()) && o.getRemindTime().getTime() <= query.getEndTime().getTime());
        }
        if (Objects.nonNull(query.getStatus())) {
            stream = stream.filter(o -> Objects.equals(o.getStatus(), query.getStatus()));
        }
        return stream.collect(Collectors.toList());
    }

    public List<ZoneRemindDTO> queryList(String userId, List<String> zoneIds) {
        List<ZoneDTO> zones = zoneService.listByUserId(userId);
        if (CollUtil.isNotEmpty(zoneIds)) {
            Set<String> zoneIdSet = new HashSet<>(zoneIds);
            zones = zones.stream().filter(o -> zoneIdSet.contains(o.getId())).collect(Collectors.toList());
        }
        return zones.parallelStream().flatMap(o -> queryAndGenerateRemind(userId, o.getId()).stream()).collect(Collectors.toList());
    }

    public List<ZoneRemindDTO> queryAndGenerateRemind(String userId, String zoneId) {
        if (StrUtil.isEmpty(zoneId)) {
            throw new BizException("空间id不能为空");
        }
        // 查询前先生成提醒项目
        generateBatchNextRemind(zoneId, userId);
        // 查询用户提醒项
        List<ZoneRemindItem> reminds = zoneRemindItemService.listByUserId(zoneId, userId);
        if (CollUtil.isEmpty(reminds)) {
            return new ArrayList<>();
        }
        List<ZoneRemind> remindTemplates = mapper.selectBatchIds(reminds.stream().map(o -> o.getZoneRemindId()).collect(Collectors.toList()));
        Map<String, ZoneRemind> remindTemplateMap = remindTemplates.stream().collect(Collectors.toMap(o -> o.getId(), o -> o, (o1, o2) -> o1));
        List<ZoneRemindDTO> result = new ArrayList<>();
        List<String> allUserIds = reminds.stream().flatMap(o -> Arrays.stream(o.getRemindUserIds().split(","))).distinct().collect(Collectors.toList());
        Map<String, User> userMap = userService.getMapByIds(allUserIds);
        for (ZoneRemindItem item : reminds) {
            List<String> userIds = Arrays.stream(item.getRemindUserIds().split(",")).distinct().collect(Collectors.toList());
            ZoneRemindDTO remind = BeanUtilEx.copy(item, ZoneRemindDTO.class);
            remind.setRemindUserIds(userIds);
            if (Objects.isNull(remind.getRepeatType())) {
                remind.setRepeatType(remindTemplateMap.getOrDefault(item.getZoneRemindId(), new ZoneRemind()).getRepeatType());
            }
            remind.setUsers(userIds.stream().map(userMap::get).collect(Collectors.toList()));
            remind.setFiles(JSON.parseArray(item.getFiles(), FileDTO.class));
            if (StrUtil.isNotEmpty(item.getCompleteFiles())) {
                remind.setCompleteFiles(JSON.parseArray(item.getCompleteFiles(), FileDTO.class));
            }
            Set<Integer> completeConditions = Arrays.stream(item.getCompleteConditions().split(",")).filter(o -> NumberUtil.isNumber(o)).map(Integer::valueOf).collect(Collectors.toSet());
            remind.setNeedRecord(completeConditions.contains(ZoneRemindEnum.CompleteCondition.CREATE_RECORD.value));
            remind.setNeedTakePicture(completeConditions.contains(ZoneRemindEnum.CompleteCondition.TAKE_PIC.value));
            remind.setNeedWaterMark(completeConditions.contains(ZoneRemindEnum.CompleteCondition.WATER_MARK.value));
            result.add(remind);
        }
        return result;
    }

    public void generateBatchNextRemind(String zoneId, String userId) {
        LambdaQueryWrapper<ZoneRemind> query = new LambdaQueryWrapper<>();
        query.eq(ZoneRemind::getZoneId, zoneId);
        query.like(ZoneRemind::getRemindUserIds, "%" + userId + "%");
        List<ZoneRemind> remindTemplates = mapper.selectList(query);
        // 获取当前已生成的提醒项
        List<String> remindTemplateIds = remindTemplates.stream().map(ZoneRemind::getId).collect(Collectors.toList());
        List<ZoneRemindItem> existsReminds = zoneRemindItemService.listByZoneRemindIds(remindTemplateIds);
        Map<String, List<ZoneRemindItem>> existsRemindMap = existsReminds.stream().collect(Collectors.groupingBy(o -> o.getZoneRemindId()));
        Map<ZoneRemind, List<ZoneRemindItem>> existsRemindGroup = new HashMap<>();
        for (ZoneRemind remindTemplate : remindTemplates) {
            existsRemindGroup.put(remindTemplate, existsRemindMap.getOrDefault(remindTemplate.getId(), new ArrayList<>()));
        }
        // 批量生成下一批提醒
        List<ZoneRemindItem> nextReminds = generateNextRemind(existsRemindGroup, userId);
        for (ZoneRemindItem nextRemind : nextReminds) {
            if (Objects.isNull(nextRemind.getId())) {
                zoneRemindItemService.add(nextRemind);
            }
        }
    }

    public ZoneRemindItem generateNextRemind(ZoneRemind remindTemplate, String userId) {
        List<ZoneRemindItem> existsReminds = zoneRemindItemService.listByZoneRemindIds(Lists.newArrayList(remindTemplate.getId()));
        List<ZoneRemindItem> result = generateNextRemind(ImmutableMap.of(remindTemplate, existsReminds), userId);
        return result.get(0);
    }

    public List<ZoneRemindItem> generateNextRemind(Map<ZoneRemind, List<ZoneRemindItem>> existsRemindGroup, String userId) {
        Date now = new Date();
        Date today = DateUtil.beginOfDay(now);
        List<ZoneRemindItem> result = new ArrayList<>();
        for (Map.Entry<ZoneRemind, List<ZoneRemindItem>> entry : existsRemindGroup.entrySet()) {
            ZoneRemind remindTemplate = entry.getKey();
            List<ZoneRemindItem> existsReminds = entry.getValue();
            ZoneRemindItem remind = BeanUtilEx.copy(remindTemplate, ZoneRemindItem.class);
            remind.setZoneRemindId(remindTemplate.getId());
            remind.setCreateUserId(userId);
            // 单次提醒
            boolean isNewRemindItem = false;
            existsReminds.sort(Comparator.comparing(ZoneRemindItem::getRemindTime, Comparator.nullsFirst(Comparator.naturalOrder())).reversed());
            List<ZoneRemindItem> hasTimeReminds = existsReminds.stream().filter(o -> Objects.nonNull(o.getRemindTime())).collect(Collectors.toList());
            if (ZoneRemindEnum.RepeatType.ONCE.value.equals(remindTemplate.getRepeatType())) {
                if (CollUtil.isEmpty(existsReminds)) {
                    remind.setRemindTime(DateUtil.parseDateTime(remindTemplate.getRemindTime()));
                    isNewRemindItem = true;
                }
            }
            // 空闲提醒
            if (ZoneRemindEnum.RepeatType.FREE.value.equals(remindTemplate.getRepeatType())) {
                if (CollUtil.isEmpty(existsReminds)) {
                    remind.setRemindTime(null);
                    isNewRemindItem = true;
                }
            }
            // 按天循环
            if (ZoneRemindEnum.RepeatType.EVERY_DAY.value.equals(remindTemplate.getRepeatType())) {
                if (CollUtil.isEmpty(hasTimeReminds) || hasTimeReminds.get(0).getRemindTime().getTime() < now.getTime()) {
                    String time = remindTemplate.getRemindTime();
                    // 兼容年月日格式
                    if (time.contains(" ")) {
                        time = time.split(" ")[1];
                    }
                    Date nextRemindTime = DateUtil.parse(DateUtil.format(today, "yyyy-MM-dd") + " " + time, "yyyy-MM-dd HH:mm:ss");
                    if (nextRemindTime.getTime() < now.getTime()) {
                        nextRemindTime = DateUtil.offsetDay(nextRemindTime, 1);
                    }
                    remind.setRemindTime(nextRemindTime);
                    isNewRemindItem = true;
                }
            }
            // 按周循环
            if (ZoneRemindEnum.RepeatType.EVERY_WEEK.value.equals(remindTemplate.getRepeatType())) {
                if (CollUtil.isEmpty(hasTimeReminds) || hasTimeReminds.get(0).getRemindTime().getTime() < now.getTime()) {
                    String[] timeParams = remindTemplate.getRemindTime().split(" ");
                    // 兼容年月日格式
                    if (timeParams[0].length() >= 10) {
                        timeParams[0] = DateUtil.dayOfWeek(DateUtil.parse(timeParams[0])) + "";
                    }
                    int[] dayOffsets = {-1,0,1,2,3,4,5};
                    int dayOffset = dayOffsets[Integer.valueOf(timeParams[0]) - 1];
                    Date weekBegin = DateUtil.beginOfWeek(today);
                    String nextRemindTimeStr = DateUtil.format(DateUtil.offsetDay(weekBegin, dayOffset), "yyyy-MM-dd") + " " + timeParams[1];
                    Date nextRemindTime = DateUtil.parse(nextRemindTimeStr, "yyyy-MM-dd HH:mm:ss");
                    if (nextRemindTime.getTime() <= now.getTime()) {
                        nextRemindTime = DateUtil.offsetWeek(nextRemindTime, 1);
                    }
                    remind.setRemindTime(nextRemindTime);
                    isNewRemindItem = true;
                }
            }
            // 按月循环
            if (ZoneRemindEnum.RepeatType.EVERY_MONTH.value.equals(remindTemplate.getRepeatType())) {
                if (CollUtil.isEmpty(hasTimeReminds) || hasTimeReminds.get(0).getRemindTime().getTime() < now.getTime()) {
                    String[] timeParams = remindTemplate.getRemindTime().split(" ");
                    // 兼容年月日格式
                    if (timeParams[0].length() >= 10) {
                        timeParams[0] = DateUtil.dayOfMonth(DateUtil.parse(timeParams[0])) + "";
                    }
                    Integer date = Integer.valueOf(timeParams[0]);
                    LocalDateTime localDate = LocalDateTime.now();
                    YearMonth yearMonth = YearMonth.of(localDate.getYear(), localDate.getMonthValue());
                    if (yearMonth.lengthOfMonth() < date) {
                        date = yearMonth.lengthOfMonth();
                    }
                    String nextRemindTimeStr = yearMonth.getYear() + "-" + yearMonth.getMonthValue() + "-" + date + " " + timeParams[1];
                    Date nextRemindTime = DateUtil.parse(nextRemindTimeStr, "yyyy-MM-dd HH:mm:ss");
                    if (nextRemindTime.getTime() <= now.getTime()) {
                        nextRemindTimeStr = yearMonth.getYear() + "-" + yearMonth.plusMonths(1).getMonthValue() + "-" + date + " " + timeParams[1];
                        nextRemindTime = DateUtil.parse(nextRemindTimeStr, "yyyy-MM-dd HH:mm:ss");
                    }
                    remind.setRemindTime(nextRemindTime);
                    isNewRemindItem = true;
                }
            }
            remind.setId(isNewRemindItem ? null : existsReminds.get(0).getId());
            result.add(remind);
        }
        return result;
    }

    @Transactional(rollbackFor = {Exception.class})
    public ZoneRemindDTO updateById(@Valid ZoneRemindUpdateDTO req) {
        ZoneRemindItem remindItem = zoneRemindItemService.getById(req.getId());
        if (Objects.isNull(remindItem)) {
            throw new BizException("该事项不存在");
        }
        // 更新模板
        ZoneRemind remindTemplate = mapper.selectById(remindItem.getZoneRemindId());
        if (Objects.isNull(remindTemplate)) {
            return add(req);
        }
        // 模板类型变化则新增模板并删除旧模板
        if (!remindTemplate.getRepeatType().equals(req.getRepeatType())) {
            mapper.deleteById(remindTemplate.getId());
            zoneRemindItemService.deleteByRemindId(remindTemplate.getId(), ZoneRemindItemEnum.Status.UN_COMPLETE.value);
            return add(req);
        }
        ZoneRemind updateTemplate = convertToZoneRemind(req);
        // 内容审核
        contentCheckService.checkText(updateTemplate.getContent());
        updateTemplate.setId(remindTemplate.getId());
        mapper.updateById(updateTemplate);
        // 更新提醒事项
        List<ZoneRemindItem> updateRemindItems = zoneRemindItemService.listByZoneRemindIds(Lists.newArrayList(remindTemplate.getId()));
        updateRemindItems = updateRemindItems.stream().filter(o -> Objects.equals(remindItem.getRemindTime(), o.getRemindTime())).collect(Collectors.toList());
        for (ZoneRemindItem updateRemindItem : updateRemindItems) {
            if (Objects.equals(remindTemplate.getRemindTime(), req.getRemindTime())) {
                ZoneRemindItem updateRemind = generateNextRemind(updateTemplate, updateRemindItem.getCreateUserId());
                updateRemind.setRemindTime(null);
                zoneRemindItemService.updateById(updateRemind);
            } else {
                zoneRemindItemService.deleteById(updateRemindItem.getId());
                ZoneRemindItem updateRemind = generateNextRemind(updateTemplate, updateRemindItem.getCreateUserId());
                updateRemind.setId(updateRemindItem.getId());
                zoneRemindItemService.add(updateRemind);
            }
        }
        return queryList(req.getCreateUserId(), Lists.newArrayList(req.getZoneId())).stream().filter(o -> o.getId().equals(remindItem.getId())).findFirst().orElse(null);
    }

    @Transactional(rollbackFor = {Exception.class})
    public void deleteById(String id) {
        if(StrUtil.isEmpty(id)) {
            throw new BizException("id不能为空");
        }
        ZoneRemindItem item = zoneRemindItemService.getById(id);
        if (Objects.isNull(item)) {
            return;
        }
        List<ZoneRemindItem> items = zoneRemindItemService.listByZoneRemindIds(Lists.newArrayList(item.getZoneRemindId()));
        Collections.sort(items, Comparator.comparing(ZoneRemindItem::getCreateTime).reversed());
        if (CollUtil.isEmpty(items) || items.get(0).getId().equals(item.getId())) {
            mapper.deleteById(item.getZoneRemindId());
        }
        zoneRemindItemService.deleteById(id);
    }

    public void completeById(@Valid ZoneRemindCompleteDTO req) {
        ZoneRemindItem remindItem = zoneRemindItemService.getById(req.getId());
        if (Objects.isNull(remindItem)) {
            throw new BizException("该事项不存在");
        }
        if (ZoneRemindItemEnum.Status.COMPLETED.value.equals(remindItem.getStatus())) {
            return;
        }
        if (CollUtil.isNotEmpty(req.getCompleteFiles())) {
            remindItem.setCompleteFiles(JSON.toJSONString(req.getCompleteFiles()));
        }
        lockerProxy.lock(() -> {
            remindItem.setStatus(ZoneRemindItemEnum.Status.COMPLETED.value);
            remindItem.setCompleteTime(new Date());
            zoneRemindItemService.updateById(remindItem);
            // 判断是否为第一个人完成
            Set<Integer> completeConditions = Arrays.stream(remindItem.getCompleteConditions().split(",")).filter(o -> NumberUtil.isNumber(o)).map(Integer::valueOf).collect(Collectors.toSet());
            if(completeConditions.contains(ZoneRemindEnum.CompleteCondition.CREATE_RECORD.value) && zoneRemindItemService.isFirstComplete(remindItem.getZoneRemindId(), remindItem.getRemindTime())) {
                SpringUtil.getApplicationContext().publishEvent(new ZoneRemindCompleteEvent(this, remindItem));
            }
            // 当任务完成方式为单人完成时，将其他人的任务也完成
            if (ZoneRemindEnum.CompleteModeEnum.SINGLE.value.equals(remindItem.getCompleteMode())) {
                List<ZoneRemindItem> otherRemindItems = zoneRemindItemService.listByZoneRemindIds(Lists.newArrayList(remindItem.getZoneRemindId()));
                otherRemindItems = otherRemindItems.stream().filter(o -> Objects.equals(o.getRemindTime(), remindItem.getRemindTime()) && ZoneRemindItemEnum.Status.UN_COMPLETE.value.equals(o.getStatus())).collect(Collectors.toList());
                for (ZoneRemindItem otherRemindItem : otherRemindItems) {
                    remindItem.setStatus(ZoneRemindItemEnum.Status.COMPLETED.value);
                    remindItem.setCompleteTime(new Date());
                    zoneRemindItemService.updateById(otherRemindItem);
                }
            }
        }, "LOCK:ZONE:REMIND:COMPLETE:" + remindItem.getZoneRemindId());
    }

    public void cancelCompleteById(String id) {
        if (StrUtil.isEmpty(id)) {
            throw new BizException("事项id不能为空");
        }
        ZoneRemindItem remindItem = zoneRemindItemService.getById(id);
        if (Objects.isNull(remindItem)) {
            throw new BizException("该事项不存在");
        }
        long now = System.currentTimeMillis();
        if (now - remindItem.getCompleteTime().getTime() > TimeUnit.MINUTES.toMillis(30)) {
            throw new BizException("代办事项完成超过半小时，不允许修改事项状态哦～");
        }
        // 判断是否协作任务全部完成
        long collaborateNum = remindItem.getRemindUserIds().split(",").length;
        List<ZoneRemindItem> multiRemindItems = zoneRemindItemService.listByZoneRemindIds(Lists.newArrayList(remindItem.getZoneRemindId()));
        long realCollaborateNum = multiRemindItems.stream().filter(o -> ZoneRemindItemEnum.Status.COMPLETED.value.equals(o.getStatus())).map(o -> o.getCreateUserId()).distinct().count();
        if (collaborateNum > 1 && collaborateNum == realCollaborateNum) {
            throw new BizException("多人代办事项完成后，不允许修改事项状态哦～");
        }
        remindItem.setStatus(ZoneRemindItemEnum.Status.UN_COMPLETE.value);
        remindItem.setCompleteTime(new Date());
        zoneRemindItemService.updateById(remindItem);
    }

    private ZoneRemind convertToZoneRemind(ZoneRemindAddDTO req) {
        String remindTimeMatch = "^(\\d{4}-\\d{2}-\\d{2} )?\\d{2}:\\d{2}:\\d{2}$";
        if (ZoneRemindEnum.RepeatType.ONCE.value.equals(req.getRepeatType())) {
            remindTimeMatch = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$";
        }
        if (ZoneRemindEnum.RepeatType.EVERY_WEEK.value.equals(req.getRepeatType())) {
            remindTimeMatch = "^(\\d|(\\d{4}-\\d{2}-\\d{2})) \\d{2}:\\d{2}:\\d{2}$";
        }
        if (ZoneRemindEnum.RepeatType.EVERY_MONTH.value.equals(req.getRepeatType())) {
            remindTimeMatch = "^(\\d|(\\d{4}-\\d{2}-\\d{2})) \\d{2}:\\d{2}:\\d{2}$";
        }
        if (!ZoneRemindEnum.RepeatType.FREE.value.equals(req.getRepeatType()) && !req.getRemindTime().matches(remindTimeMatch)) {
            throw new BizException("提醒时间格式不正确");
        }
        if (StrUtil.isNotEmpty(req.getRemindTime()) && req.getRemindTime().contains(" 24:")) {
            req.setRemindTime(req.getRemindTime().replace(" 24:", " 00:"));
        }
        if (CollUtil.isEmpty(req.getFiles())) {
            req.setFiles(new ArrayList<>());
        }
        if (CollUtil.isEmpty(req.getRemindUserIds())) {
            req.setRemindUserIds(new ArrayList<>());
        }
        if (!req.getRemindUserIds().contains(req.getCreateUserId())) {
            req.getRemindUserIds().add(req.getCreateUserId());
        }
        ZoneRemind remind = BeanUtilEx.copy(req, ZoneRemind.class);
        remind.setId(SnowflakeIdUtil.getIdStr());
        remind.setRemindUserIds(String.join(",", req.getRemindUserIds()));
        remind.setFiles(JSON.toJSONString(req.getFiles()));
        List<Integer> completeConditions = new ArrayList<>();
        if (Boolean.TRUE.equals(req.getNeedRecord())) {
            completeConditions.add(ZoneRemindEnum.CompleteCondition.CREATE_RECORD.value);
        }
        if (Boolean.TRUE.equals(req.getNeedTakePicture())) {
            completeConditions.add(ZoneRemindEnum.CompleteCondition.TAKE_PIC.value);
        }
        if (Boolean.TRUE.equals(req.getNeedWaterMark())) {
            completeConditions.add(ZoneRemindEnum.CompleteCondition.WATER_MARK.value);
        }
        remind.setCompleteConditions(completeConditions.stream().map(o -> o.toString()).collect(Collectors.joining(",")));
        return remind;
    }

    public ZoneRemindDTO getById(String id, String userId) {
        ZoneRemindItem remindItem = zoneRemindItemService.getById(id);
        if (Objects.isNull(remindItem)) {
            throw new BizException("该提醒事项不存在");
        }
        return queryList(userId, Lists.newArrayList(remindItem.getZoneId())).stream().filter(o -> o.getZoneRemindId().equals(remindItem.getZoneRemindId()) && Objects.equals(o.getRemindTime(), remindItem.getRemindTime())).findFirst().orElse(null);
    }

    public void generateNotFreeRemind() {
        List<User> users = userService.queryAll();
        for (User user : users) {
            if (Objects.isNull(userDeviceService.get(user.getId()))) {
                continue;
            }
            LambdaQueryWrapper<ZoneRemind> query = new LambdaQueryWrapper<>();
            query.ne(ZoneRemind::getRepeatType, ZoneRemindEnum.RepeatType.FREE.value);
            query.like(ZoneRemind::getRemindUserIds, "%" + user.getId() + "%");
            List<ZoneRemind> remindTemplates = mapper.selectList(query);
            // 生成的提醒项
            for (ZoneRemind remindTemplate : remindTemplates) {
                generateRemindExecutor.execute(() -> {
                    ZoneRemindItem zoneRemindItem = generateNextRemind(remindTemplate, user.getId());
                    if (Objects.isNull(zoneRemindItem.getId())) {
                        zoneRemindItemService.add(zoneRemindItem);
                    }
                });
            }
        }
    }

    public void pushToClient() {
        Date now = new Date();
        List<ZoneRemindItem> reminds = zoneRemindItemService.queryWaitNotices();
        for (ZoneRemindItem remind : reminds) {
            ZoneRemind remindTemplate = mapper.selectById(remind.getZoneRemindId());
            if (Objects.isNull(remindTemplate)) {
                continue;
            }
            try {
                if (remind.getRemindTime().getTime() - now.getTime() <= TimeUnit.MINUTES.toMillis(1)) {
                    // 推送
                    PushAppNoticeDTO pushReq = new PushAppNoticeDTO();
                    pushReq.setId(SnowflakeIdUtil.getIdStr());
                    pushReq.setTitle("温馨提醒您");
                    pushReq.setBody(remind.getContent());
                    pushReq.setUrl("td://schedule/remind?scheduleId='" + remind.getId() + "'&zoneId='" + remind.getZoneId() + "'");
                    pushReq.setUserId(remind.getCreateUserId());
                    pushService.pushAppNotice(pushReq);
                    // 修改为已推送
                    remind.setHasNotice(true);
                    zoneRemindItemService.updateById(remind);
                }
            } catch (Exception e) {
                log.error("提醒推送异常, id: " + remind.getId(), e);
            }
        }
    }
}
